package com.wstuo.common.config.util;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;

import org.apache.struts2.json.JSONResult;
import org.apache.struts2.json.JSONUtil;
import org.apache.struts2.json.SerializationParams;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
 * 国际化时区转换处理
 * @author QXY
 */
public class JSONResults extends JSONResult {
	private static final long serialVersionUID = 8624350183189931165L;
	private static final Logger LOG = LoggerFactory.getLogger(JSONResult.class);

	private String defaultEncoding = "ISO-8859-1";
	private List<Pattern> includeProperties;
	private List<Pattern> excludeProperties;
	private String root;
	private boolean wrapWithComments;
	private boolean prefix;
	private boolean enableSMD = false;
	private boolean enableGZIP = false;
	private boolean ignoreHierarchy = true;
	private boolean ignoreInterfaces = true;
	private boolean enumAsBean = false;
	private boolean noCache = false;
	private boolean excludeNullProperties = false;
	private int statusCode;
	private int errorCode;
	private String callbackParameter;
	private String contentType;
	private String wrapPrefix;
	private String wrapSuffix;

	@Inject("struts.i18n.encoding")
	public void setDefaultEncoding(String val) {
		this.defaultEncoding = val;
	}

	public List<Pattern> getExcludePropertiesList() {
		return this.excludeProperties;
	}

	public void setExcludeProperties(String commaDelim) {
		//List<String> excludePatterns = JSONUtil.asList(commaDelim);
	    // | modified by william
	    // | date 2014-6-25 16:58:22
	    // | cause of upgrade Struts2
	    // | JSONUtil -> asList method undefined
	    Set<String> excludePatterns = JSONUtil.asSet(commaDelim);
		if (excludePatterns != null) {
			this.excludeProperties = new ArrayList(excludePatterns.size());
			for (String pattern : excludePatterns)
				this.excludeProperties.add(Pattern.compile(pattern));
		}
	}

	public List<Pattern> getIncludePropertiesList() {
		return this.includeProperties;
	}

	public void setIncludeProperties(String commaDelim) {
		//List<String> includePatterns = JSONUtil.asList(commaDelim);
	    // | modified by william
        // | date 2014-6-25 16:58:22
        // | cause of upgrade Struts2
        // | JSONUtil -> asList method undefined
	    Set<String> includePatterns = JSONUtil.asSet(commaDelim);
		HashMap existingPatterns;
		if (includePatterns != null) {
			this.includeProperties = new ArrayList(includePatterns.size());

			existingPatterns = new HashMap();

			for (String pattern : includePatterns) {
				String[] patternPieces = pattern.split("\\\\\\.");

				String patternExpr = "";
				for (String patternPiece : patternPieces) {
					if (patternExpr.length() > 0) {
						patternExpr = patternExpr + "\\.";
					}
					patternExpr = patternExpr + patternPiece;

					if (!existingPatterns.containsKey(patternExpr)) {
						existingPatterns.put(patternExpr, patternExpr);

						if (patternPiece.endsWith("\\]")) {
							this.includeProperties.add(Pattern
									.compile(patternExpr.substring(0,
											patternPiece.lastIndexOf("\\["))));

							if (LOG.isDebugEnabled()) {
								LOG.debug(
										"Adding include property expression:  "
												+ patternExpr
														.substring(
																0,
																patternPiece
																		.lastIndexOf("\\[")),
										new String[0]);
							}
						}

						this.includeProperties
								.add(Pattern.compile(patternExpr));

						if (LOG.isDebugEnabled())
							LOG.debug("Adding include property expression:  "
									+ patternExpr, new String[0]);
					}
				}
			}
		}
	}

	public void execute(ActionInvocation invocation) throws Exception {
		ActionContext actionContext = invocation.getInvocationContext();
		HttpServletRequest request = (HttpServletRequest) actionContext
				.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest");
		HttpServletResponse response = (HttpServletResponse) actionContext
				.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse");
		try {
			Object rootObject;
			if (this.enableSMD) {
				rootObject = writeSMD(invocation);
			} else {
				if (this.root != null) {
					ValueStack stack = invocation.getStack();
					rootObject = stack.findValue(this.root);
				} else {
					rootObject = invocation.getAction();
				}
			}
			String json = JSONUtil.serialize(rootObject,
					this.excludeProperties, this.includeProperties,
					this.ignoreHierarchy, this.enumAsBean,
					this.excludeNullProperties);

			JsonConfig jsonConfig = new JsonConfig();
			jsonConfig.setIgnoreDefaultExcludes(false);
			jsonConfig.registerJsonValueProcessor(Date.class,
					new MyJsonValueProcessor());

			if (rootObject != null) {
				String className = rootObject.getClass().getName();// 对象类型
				if (className.indexOf("java.util.ArrayList") != -1) {
					rootObject = JSONArray.fromObject(rootObject, jsonConfig);
					json = rootObject.toString();
				} else if (className.indexOf("java.lang.String") != -1
						|| className.indexOf("java.lang.Integer") != -1
						|| className.indexOf("java.lang.Long") != -1
						|| className.indexOf("java.lang.Boolean") != -1) {
					json = JSONUtil.serialize(rootObject,
							this.excludeProperties, this.includeProperties,
							this.ignoreHierarchy, this.enumAsBean,
							this.excludeNullProperties);
				} else {
					try {
						rootObject = JSONObject.fromObject(rootObject,
								jsonConfig);
						json = rootObject.toString();
					} catch (Exception e) {
						if (e.getMessage().indexOf("Use JSONArray instead") != -1) {
							try {
								rootObject = JSONArray.fromObject(rootObject,
										jsonConfig);
								json = rootObject.toString();
							} catch (Exception e2) {
								LOG.debug("JSONArray FromObject Error!");
							}

						}
					}

				}
			}
			json = addCallbackIfApplicable(request, json);

			boolean writeGzip = (this.enableGZIP)
					&& (JSONUtil.isGzipInRequest(request));

			writeToResponse(response, json, writeGzip);
		} catch (IOException exception) {
			LOG.error(exception.getMessage(), exception, new String[0]);
			throw exception;
		}
	}

	protected void writeToResponse(HttpServletResponse response, String json,
			boolean gzip) throws IOException {
		JSONUtil.writeJSONToResponse(new SerializationParams(response,
				getEncoding(), isWrapWithComments(), json, false, gzip,
				this.noCache, this.statusCode, this.errorCode, this.prefix,
				this.contentType, this.wrapPrefix, this.wrapSuffix));
	}

	protected org.apache.struts2.json.smd.SMD writeSMD(
			ActionInvocation invocation) {
		ActionContext actionContext = invocation.getInvocationContext();
		HttpServletRequest request = (HttpServletRequest) actionContext
				.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest");

		Object rootObject = null;
		if (this.root != null) {
			ValueStack stack = invocation.getStack();
			rootObject = stack.findValue(this.root);
		} else {
			rootObject = invocation.getAction();
		}

		Class clazz = rootObject.getClass();
		org.apache.struts2.json.smd.SMD smd = new org.apache.struts2.json.smd.SMD();

		smd.setServiceUrl(request.getRequestURI());

		org.apache.struts2.json.annotations.SMD smdAnnotation = (org.apache.struts2.json.annotations.SMD) clazz
				.getAnnotation(org.apache.struts2.json.annotations.SMD.class);
		if (smdAnnotation != null) {
			smd.setObjectName(smdAnnotation.objectName());
			smd.setServiceType(smdAnnotation.serviceType());
			smd.setVersion(smdAnnotation.version());
		}

		Method[] methods = JSONUtil
				.listSMDMethods(clazz, this.ignoreInterfaces);

		for (Method method : methods) {
			org.apache.struts2.json.annotations.SMDMethod smdMethodAnnotation = (org.apache.struts2.json.annotations.SMDMethod) method
					.getAnnotation(org.apache.struts2.json.annotations.SMDMethod.class);

			if ((smdMethodAnnotation != null)
					&& (!shouldExcludeProperty(method.getName()))) {
				String methodName = smdMethodAnnotation.name().length() == 0 ? method
						.getName() : smdMethodAnnotation.name();

				org.apache.struts2.json.smd.SMDMethod smdMethod = new org.apache.struts2.json.smd.SMDMethod(
						methodName);

				smd.addSMDMethod(smdMethod);

				int parametersCount = method.getParameterTypes().length;
				if (parametersCount > 0) {
					Annotation[][] parameterAnnotations = method
							.getParameterAnnotations();

					for (int i = 0; i < parametersCount; i++) {
						// org.apache.struts2.json.annotations.SMDMethodParameter
						// smdMethodParameterAnnotation =
						// getSMDMethodParameterAnnotation(parameterAnnotations[i]);

						String paramName = "p" + i;

						smdMethod
								.addSMDMethodParameter(new org.apache.struts2.json.smd.SMDMethodParameter(
										paramName));
					}

				}

			} else if (LOG.isDebugEnabled()) {
				LOG.debug("Ignoring property " + method.getName(),
						new String[0]);
			}
		}
		return smd;
	}

	@SuppressWarnings("unused")
	private org.apache.struts2.json.annotations.SMDMethodParameter getSMDMethodParameterAnnotation(
			Annotation[] annotations) {
		org.apache.struts2.json.annotations.SMDMethodParameter methodParameter = null;
		for (Annotation annotation : annotations) {
			if ((annotation instanceof org.apache.struts2.json.annotations.SMDMethodParameter)) {
				methodParameter = (org.apache.struts2.json.annotations.SMDMethodParameter) annotation;
				break;
			}
		}
		return methodParameter;
	}

	private boolean shouldExcludeProperty(String expr) {
		boolean result = false;
		if (this.excludeProperties != null) {
			for (Pattern pattern : this.excludeProperties) {
				if (pattern.matcher(expr).matches()){
					result = true;
					break;
				}
			}
		}
		return result;
	}

	protected String getEncoding() {
		String encoding = this.defaultEncoding;

		if (encoding == null) {
			encoding = System.getProperty("file.encoding");
		}

		if (encoding == null) {
			encoding = "UTF-8";
		}

		return encoding;
	}

	protected String addCallbackIfApplicable(HttpServletRequest request,
			String json) {
		if ((this.callbackParameter != null)
				&& (this.callbackParameter.length() > 0)) {
			String callbackName = request.getParameter(this.callbackParameter);
			if ((callbackName != null) && (callbackName.length() > 0))
				json = callbackName + "(" + json + ")";
		}
		return json;
	}

	public String getRoot() {
		return this.root;
	}

	public void setRoot(String root) {
		this.root = root;
	}

	public boolean isWrapWithComments() {
		return this.wrapWithComments;
	}

	public void setWrapWithComments(boolean wrapWithComments) {
		this.wrapWithComments = wrapWithComments;
	}

	public boolean isEnableSMD() {
		return this.enableSMD;
	}

	public void setEnableSMD(boolean enableSMD) {
		this.enableSMD = enableSMD;
	}

	public void setIgnoreHierarchy(boolean ignoreHierarchy) {
		this.ignoreHierarchy = ignoreHierarchy;
	}

	public void setIgnoreInterfaces(boolean ignoreInterfaces) {
		this.ignoreInterfaces = ignoreInterfaces;
	}

	public void setEnumAsBean(boolean enumAsBean) {
		this.enumAsBean = enumAsBean;
	}

	public boolean isEnumAsBean() {
		return this.enumAsBean;
	}

	public boolean isEnableGZIP() {
		return this.enableGZIP;
	}

	public void setEnableGZIP(boolean enableGZIP) {
		this.enableGZIP = enableGZIP;
	}

	public boolean isNoCache() {
		return this.noCache;
	}

	public void setNoCache(boolean noCache) {
		this.noCache = noCache;
	}

	public boolean isIgnoreHierarchy() {
		return this.ignoreHierarchy;
	}

	public boolean isExcludeNullProperties() {
		return this.excludeNullProperties;
	}

	public void setExcludeNullProperties(boolean excludeNullProperties) {
		this.excludeNullProperties = excludeNullProperties;
	}

	public void setStatusCode(int statusCode) {
		this.statusCode = statusCode;
	}

	public void setErrorCode(int errorCode) {
		this.errorCode = errorCode;
	}

	public void setCallbackParameter(String callbackParameter) {
		this.callbackParameter = callbackParameter;
	}

	public String getCallbackParameter() {
		return this.callbackParameter;
	}

	public void setPrefix(boolean prefix) {
		this.prefix = prefix;
	}

	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	public String getWrapPrefix() {
		return this.wrapPrefix;
	}

	public void setWrapPrefix(String wrapPrefix) {
		this.wrapPrefix = wrapPrefix;
	}

	public String getWrapSuffix() {
		return this.wrapSuffix;
	}

	public void setWrapSuffix(String wrapSuffix) {
		this.wrapSuffix = wrapSuffix;
	}
}
